import json
from typing import List, Dict, Tuple, Optional
from flask import Flask, request, jsonify
from uuid import uuid4
app = Flask(__name__)

class Container:
    def __init__(self, length: float, width: float, height: float, max_weight: float):
        self.length = length
        self.width = width
        self.height = height
        self.max_weight = max_weight
        self.current_weight = 0.0
        self.occupied_space = []  # 记录已占用的空间区域
        self.regions = {}  # 记录每个物品类型的区域边界

class Item:
    def __init__(
        self,
        id: str,
        name: str,
        length: float,
        width: float,
        height: float,
        weight: float,
        quantity: int,
        stack_limit: int,
        color: str,  # 新增：货品颜色
        hw_id:str, # 新增:货主编码
        rotation: int = 0,  # 新增：旋转方向（0°, 90°, 180°）
        priority: int = 1,  # 新增：优先级
        xh:int=1,
      
       
    ):
        self.id = id
        self.name = name
        self.length = length
        self.width = width
        self.height = height
        self.weight = weight
        self.quantity = quantity
        self.stack_limit = stack_limit
        self.color = color
        self.rotation = rotation  # 0: 默认方向, 1: 90°, 2: 180°
        self.priority = priority
        self.placed_quantity = 0  # 已装载的数量
        self.positions = []  # 记录每个物品的位置和旋转状态
        self.overflow_quantity = 0  # 记录无法装载的数量
        self.xh=xh  # 所属的箱号
        self.hw_id=hw_id  # 所属的货主

    def get_dimensions(self) -> Tuple[float, float, float]:
        """根据旋转方向返回物品的实际长、宽、高"""
        if self.rotation == 0:
            return (self.length, self.width, self.height)
        elif self.rotation == 1:  # 90°
            return (self.width, self.length, self.height)
        elif self.rotation == 2:  # 180°
            return (self.length, self.width, self.height)  # 与0°相同
        else:
            raise ValueError("旋转方向必须是 0、1 或 2")

class PackingAlgorithm:
    def __init__(self, container: Container, items: List[Item], strategy: str = "volume"):
        self.container = container
        self.items = items
        self.strategy = strategy  # 装载策略：volume, weight, mixed
        self.overflow_items = []  # 记录无法装载的物品

    def sort_items(self) -> None:
        """根据装载策略对物品排序"""
        if self.strategy == "volume":
            self.items.sort(key=lambda x: x.length * x.width * x.height, reverse=True)
        elif self.strategy == "weight":
            self.items.sort(key=lambda x: x.weight, reverse=True)
        elif self.strategy == "mixed":
            self.items.sort(key=lambda x: (x.priority, x.length * x.width * x.height), reverse=True)

    def assign_regions(self) -> None:
        """为每个物品类型分配固定区域（动态计算区域高度）"""
        unique_items = list(set(item.id for item in self.items))
        region_length = self.container.length / len(unique_items)  # 均分区域长度

        for i, item_id in enumerate(unique_items):
            # 获取物品的 stack_limit 和高度
            item = next(item for item in self.items if item.id == item_id)
            region_height = item.height * item.stack_limit
            if region_height > self.container.height:
                region_height = self.container.height

            self.container.regions[item_id] = {
                "min_x": i * region_length,
                "max_x": (i + 1) * region_length,
                "min_y": 0,
                "max_y": self.container.width,
                "min_z": 0,
                "max_z": region_height  # 动态计算的高度
            }
            print(f"区域分配: 物品 {item_id} -> x={self.container.regions[item_id]['min_x']}~{self.container.regions[item_id]['max_x']}, z_max={region_height}")

    def is_valid_position(self, item: Item, x: float, y: float, z: float) -> bool:
        """检查物品是否可以放置在指定位置（考虑旋转方向）"""
        # 获取旋转后的实际尺寸
        length, width, height = item.get_dimensions()

        # 检查是否在所属区域内
        region = self.container.regions.get(item.id)
        if not region or not (
            region["min_x"] <= x <= region["max_x"] - length and
            region["min_y"] <= y <= region["max_y"] - width and
            region["min_z"] <= z <= region["max_z"] - height
        ):
            return False

        # 检查堆叠限制
        stack_count = 0
        for occupied in self.container.occupied_space:
            if (x == occupied["x"] and y == occupied["y"]):
                stack_count += 1
        if stack_count >= item.stack_limit:
            return False

        # 检查重量限制
        if self.container.current_weight + item.weight > self.container.max_weight:
            return False

        # 检查空间冲突
        for occupied in self.container.occupied_space:
            if not (
                x + length <= occupied["x"] or
                occupied["x"] + occupied["length"] <= x or
                y + width <= occupied["y"] or
                occupied["y"] + occupied["width"] <= y or
                z + height <= occupied["z"] or
                occupied["z"] + occupied["height"] <= z
            ):
                return False

        return True

    def find_position(self, item: Item) -> Optional[Tuple[float, float, float, int]]:
        """为物品寻找合适的放置位置（考虑旋转方向）"""
        region = self.container.regions.get(item.id)
        if not region:
            return None

        # 检查区域内已堆叠的层数
        region_stack_count = 0
        for occupied in self.container.occupied_space:
            if "region_id" in occupied and occupied["region_id"] == item.id:
                region_stack_count += 1
        if region_stack_count >= 2:  # stack_limit=2
            return None  # 区域内堆叠层数已满

        # 尝试所有可能的旋转方向
        for rotation in [0, 1, 2]:
            item.rotation = rotation
            length, width, height = item.get_dimensions()

            # 确保旋转后的尺寸不超过区域边界
            if length > region["max_x"] - region["min_x"] or width > region["max_y"] - region["min_y"] or height > region["max_z"] - region["min_z"]:
                continue  # 跳过尺寸超限的旋转方向

            for z in range(0, int(region["max_z"]), int(height)):  # 动态计算 z 坐标
                for x in range(
                    int(region["min_x"]),
                    int(region["max_x"] - length) + 1,
                    int(length)  # 紧密排列 x 坐标
                ):
                    for y in range(0, int(region["max_y"] - width) + 1, int(width)):
                        if self.is_valid_position(item, x, y, z):
                            return (x, y, z, rotation)
        return None

    def pack_items(self) -> None:
        """执行装载算法，处理物品数量"""
        self.assign_regions()
        self.sort_items()
        for item in self.items:
            remaining_quantity = item.quantity
            while remaining_quantity > 0:
                position = self.find_position(item)
                if position:
                    x, y, z, rotation = position
                    # 放置物品
                    item.positions.append({
                        "x": x,
                        "y": y,
                        "z": z,
                        "rotation": rotation  # 记录旋转方向
                    })
                    length, width, height = item.get_dimensions()
                    self.container.occupied_space.append({
                        "x": x,
                        "y": y,
                        "z": z,
                        "length": length,
                        "width": width,
                        "height": height,
                    })
                    self.container.current_weight += item.weight
                    item.placed_quantity += 1
                    remaining_quantity -= 1
                else:
                    # 无法装载更多，剩余放入溢出
                    item.overflow_quantity = remaining_quantity
                    self.overflow_items.append(item)
                    break

    def generate_json_output(self) -> Dict:
        """生成JSON格式的输出数据（包含颜色和旋转方向）"""
        placed_items = []
        for item in self.items:
            for pos in item.positions:
                length, width, height = item.get_dimensions() if "rotation" in pos else (item.length, item.width, item.height)
                placed_items.append({
                    "xh": item.xh,
                    "yzb": pos["y"],
                    "xzb": pos["x"],
                    "zzb": pos["z"],
                    "length": length,
                    "width": width,
                    "height": height,
                    "hw_id": item.hw_id,  # 物品id
                    "pk": str(uuid4()) , # 随机生成的标识,
                    "hpys": item.color,  # 货品颜色
                    "rotation": pos.get("rotation", 0),  # 旋转方向
                    "region": item.id  # 标注所属区域
                })

        return {
            "kd": self.container.width,
            "zd": self.container.length,
            "gd": self.container.height,
            "dt_zxjhhwwzmxList": placed_items,
            "regions": self.container.regions  # 输出区域划分信息
        }

    def generate_statistics(self) -> Dict:
        """生成统计报告"""
        total_placed = sum(item.placed_quantity for item in self.items)
        total_volume = sum(
            item.length * item.width * item.height * item.placed_quantity 
            for item in self.items
        )
        container_volume = self.container.length * self.container.width * self.container.height
        volume_ratio = (total_volume / container_volume) * 100
        total_weight = sum(item.weight * item.placed_quantity for item in self.items)
        weight_ratio = (total_weight / self.container.max_weight) * 100
        total_quantity = sum(item.quantity for item in self.items)
        overflow_quantity = sum(item.overflow_quantity for item in self.overflow_items) if self.overflow_items else 0

        return {
             "zjs": total_quantity,
            "zpjs": total_placed,
            "ycjs": overflow_quantity,
            "ztj": total_volume,
            "zmz": total_weight,
            "rjl": f"{volume_ratio:.2f}%",
            "zjl": f"{weight_ratio:.2f}%",
            "ycl": f"{(overflow_quantity / total_quantity) * 100:.2f}%",
        }

def save_json_to_file(data: Dict, file_path: str) -> None:
    """将JSON数据保存到文件"""
    with open(file_path, "w", encoding="utf-8") as f:
        json.dump(data, f, indent=2, ensure_ascii=False)

# # 示例用法
# if __name__ == "__main__":
#     # 定义容器
#     container = Container(length=420, width=230, height=250, max_weight=2000)

#     # 定义物品（新增颜色和旋转方向）
#     items = [
#         Item(id="香蕉", name="香蕉", length=30.0, width=24, height=18, weight=1, quantity=180, stack_limit=3, color="#0011ff", rotation=1),
#     ]

#     # 执行装载算法
#     packing = PackingAlgorithm(container, items, strategy="volume")
#     packing.pack_items()

#     # 生成JSON数据并保存到文件
#     output_data = packing.generate_json_output()
#     save_json_to_file(output_data, "D:\\work\\cdkyhx\\packing_plan_client\\src\\views\\3d\\data.json")

#     # 打印统计报告
#     print("统计报告:")
#     stats = packing.generate_statistics()
#     for key, value in stats.items():
#         print(f"{key}: {value}")
#     print("\nJSON数据已保存到 output.json 文件。")

@app.route('/pack', methods=['POST'])
def pack():
    data = request.json
    container = Container(
        length=data['container']['length'],
        width=data['container']['width'],
        height=data['container']['height'],
        max_weight=data['container']['maxWeight']
    )
    items = []
    x=1
    for item_data in data['items']:
        items.append(Item(
            id=item_data['id'],
            name=item_data['name'],
            length=item_data['length'],
            width=item_data['width'],
            height=item_data['height'],
            weight=item_data['weight'],
            quantity=item_data['quantity'],
            stack_limit=item_data['stack_limit'],
            color=item_data['color'],
            rotation=item_data.get('rotation', 0),
            priority=item_data.get('priority', 1),
            xh=x,
            hw_id=item_data['hw_id'],
             
        ))
        x+=1
   

    packing = PackingAlgorithm(container, items, strategy=data.get('strategy', 'volume'))
    packing.pack_items()
    return jsonify({
        "result": packing.generate_json_output(),
        "statistics": packing.generate_statistics()
    })

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)